from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, HttpUrl
from typing import Optional
import uvicorn
import os
import tempfile
import requests
from urllib.parse import urlparse
from run_eval import evaluate_pronunciation
import numpy as np
import torch
import logging
import base64

# 配置日志
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

app = FastAPI(
    title="发音测评API",
    description="基于AI的英文发音测评系统",
    version="1.0.0"
)

# 定义请求模型
class EvalRequest(BaseModel):
    text: str  # 参考文本
    lang: str = "en"  # 语言代码（en/de/zh）
    audio_url: Optional[str] = None  # 音频文件URL地址
    audio_base64: Optional[str] = None  # base64编码的音频数据
    
    class Config:
        json_schema_extra = {
            "example": {
                "text": "hello world",
                "lang": "en",
                "audio_url": "https://example.com/audio.wav"
                # 或者使用 "audio_base64": "base64编码的音频数据"
            }
        }

# 添加请求日志中间件
@app.middleware("http")
async def log_requests(request, call_next):
    logger.info(f"收到请求: {request.method} {request.url}")
    logger.info(f"请求头: {dict(request.headers)}")
    
    response = await call_next(request)
    logger.info(f"响应状态码: {response.status_code}")
    return response

def to_builtin_type(obj):
    if isinstance(obj, dict):
        return {k: to_builtin_type(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [to_builtin_type(v) for v in obj]
    elif isinstance(obj, np.generic):
        return obj.item()
    else:
        return obj

def download_audio_from_url(url: str, timeout: int = 30) -> str:
    """
    从URL下载音频文件到临时文件
    返回临时文件路径
    """
    try:
        # 验证URL格式
        parsed_url = urlparse(url)
        if not parsed_url.scheme or not parsed_url.netloc:
            raise ValueError("无效的URL格式")
        
        # 发送请求下载文件
        response = requests.get(url, timeout=timeout, stream=True)
        response.raise_for_status()
        
        # 从URL或Content-Type推断文件扩展名
        content_type = response.headers.get('content-type', '').lower()
        file_extension = None
        
        # 首先尝试从URL路径获取扩展名
        url_path = parsed_url.path.lower()
        if url_path.endswith(('.wav', '.mp3', '.flac', '.m4a', '.ogg', '.aac', '.wma')):
            file_extension = '.' + url_path.split('.')[-1]
        # 然后尝试从Content-Type获取
        elif 'audio/wav' in content_type or 'audio/wave' in content_type:
            file_extension = '.wav'
        elif 'audio/mpeg' in content_type or 'audio/mp3' in content_type:
            file_extension = '.mp3'
        elif 'audio/flac' in content_type:
            file_extension = '.flac'
        elif 'audio/m4a' in content_type or 'audio/mp4' in content_type:
            file_extension = '.m4a'
        elif 'audio/ogg' in content_type:
            file_extension = '.ogg'
        else:
            # 默认使用.wav
            file_extension = '.wav'
        
        # 创建临时文件
        with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as tmp_file:
            # 分块写入文件
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    tmp_file.write(chunk)
            tmp_path = tmp_file.name
        
        return tmp_path
        
    except requests.exceptions.RequestException as e:
        raise HTTPException(status_code=400, detail=f"下载音频文件失败: {str(e)}")
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"处理URL时发生错误: {str(e)}")

def save_base64_audio(audio_base64: str, file_extension: str = '.wav') -> str:
    """
    将base64编码的音频数据保存到临时文件
    返回临时文件路径
    """
    try:
        # 解码base64数据
        audio_data = base64.b64decode(audio_base64)
        
        # 创建临时文件
        with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as tmp_file:
            tmp_file.write(audio_data)
            tmp_path = tmp_file.name
        
        return tmp_path
        
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"处理base64音频数据失败: {str(e)}")

@app.get("/health")
async def health_check():
    """健康检查接口"""
    return {
        "status": "healthy",
        "cuda_available": torch.cuda.is_available(),
        "gpu_count": torch.cuda.device_count() if torch.cuda.is_available() else 0
    }

@app.get("/info")
async def system_info():
    """系统信息接口"""
    return {
        "gpu_available": torch.cuda.is_available(),
        "gpu_count": torch.cuda.device_count() if torch.cuda.is_available() else 0,
        "gpu_name": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "No GPU",
        "python_version": f"{__import__('sys').version_info.major}.{__import__('sys').version_info.minor}",
        "torch_version": torch.__version__
    }

@app.post("/eval")
async def eval_pronunciation(request: EvalRequest):
    """
    发音测评接口
    
    支持两种音频输入方式：
    1. 提供音频文件URL地址（audio_url参数）
    2. 提供base64编码的音频数据（audio_base64参数）
    
    注意：audio_url和audio_base64两个参数必须提供其中一个，不能同时为空或同时提供
    
    返回三个维度分数：
    - completeness: 完整度（0-100）
    - accuracy: 准确度（0-100）  
    - fluency: 流利度（0-100）
    - overall_score: 总分数（0-100）
    
    支持的音频格式：wav, mp3, flac, m4a, ogg等
    
    请求示例：
    {
        "text": "hello world",
        "lang": "en",
        "audio_url": "https://example.com/audio.wav"
    }
    
    或者：
    {
        "text": "hello world", 
        "lang": "en",
        "audio_base64": "base64编码的音频数据"
    }
    """
    
    # 验证参数：必须提供audio_url或audio_base64其中一个，但不能同时提供
    if not request.audio_url and not request.audio_base64:
        raise HTTPException(
            status_code=400, 
            detail="必须提供audio_url（URL地址）或audio_base64（base64编码数据）参数之一"
        )
    
    if request.audio_url and request.audio_base64:
        raise HTTPException(
            status_code=400, 
            detail="不能同时提供audio_url和audio_base64参数，请选择其中一种方式"
        )
    
    tmp_path = None
    try:
        # 处理URL下载方式  
        if request.audio_url:
            tmp_path = download_audio_from_url(request.audio_url)
        
        # 处理base64编码方式
        elif request.audio_base64:
            tmp_path = save_base64_audio(request.audio_base64)
        
        # 调用评估
        result = evaluate_pronunciation(tmp_path, request.text, request.lang)
        
        if result is None:
            raise HTTPException(status_code=500, detail="评估失败")
        
        result = to_builtin_type(result)
        return JSONResponse(content=result)
        
    except HTTPException:
        # 重新抛出HTTP异常
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
    
    finally:
        # 清理临时文件
        if tmp_path and os.path.exists(tmp_path):
            try:
                os.remove(tmp_path)
            except:
                pass  # 忽略删除失败的错误

# 运行方法：
# 1. 安装依赖： pip install fastapi uvicorn requests
# 2. 启动服务： uvicorn fastapi_app:app --host 0.0.0.0 --port 5001
# 3. 使用接口：
#    方式1 - 使用URL:
#    curl -X POST http://127.0.0.1:5001/eval \
#      -H "Content-Type: application/json" \
#      -d '{"text":"hello world","lang":"en","audio_url":"https://example.com/audio.wav"}'
#    
#    方式2 - 使用base64:
#    curl -X POST http://127.0.0.1:5001/eval \
#      -H "Content-Type: application/json" \
#      -d '{"text":"hello world","lang":"en","audio_base64":"base64编码的音频数据"}'
#    
#    Swagger文档： http://127.0.0.1:5001/docs 